package com.sherllinhe.business.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
//import com.sherllinhe.common.context.LoginMemberContext;
import com.sherllinhe.business.domain.*;
import com.sherllinhe.business.dto.ConfirmOrderMQDdto;
import com.sherllinhe.business.enums.ConfirmOrderStatusEnum;
import com.sherllinhe.business.enums.RedisKeyPreEnum;
import com.sherllinhe.business.enums.SeatColEnum;
import com.sherllinhe.business.enums.SeatTypeEnum;
import com.sherllinhe.business.req.ConfirmOrderTicketReq;
import com.sherllinhe.common.context.LoginMemberContext;
import com.sherllinhe.common.exception.BusinessException;
import com.sherllinhe.common.exception.BusinessExceptionEnum;
import com.sherllinhe.common.resp.PageResp;
import com.sherllinhe.common.util.SnowUtil;
import com.sherllinhe.business.mapper.ConfirmOrderMapper;
import com.sherllinhe.business.req.ConfirmOrderQueryReq;
import com.sherllinhe.business.req.ConfirmOrderDoReq;
import com.sherllinhe.business.resp.ConfirmOrderQueryResp;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 功能：
 * 作者：Sherllin He
 * 日期：
 */
@Slf4j
@Service
public class ConfirmOrderService {
    @Autowired
    private ConfirmOrderMapper confirmOrderMapper;
    @Autowired
    private DailyTrainTicketService dailyTrainTicketService;
    @Autowired
    private DailyTrainCarriageService dailyTrainCarriageService;
    @Autowired
    private DailyTrainSeatService dailyTrainSeatService;
    @Autowired
    private AfterConfirmOrderService afterConfirmOrderService;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private SkTokenService skTokenService;
    // 新增和编辑确认订单
    public void save(ConfirmOrderDoReq req){
        DateTime now = DateTime.now();
        ConfirmOrder confirmOrder = BeanUtil.copyProperties(req, ConfirmOrder.class);

        //判断是新增还是更新
        //confirmOrder.setMemberId(LoginMemberContext.getId());
        if(ObjUtil.isNull(confirmOrder.getId())) {
            //新增
            confirmOrder.setId(SnowUtil.getSnowflakeNextId());
            confirmOrder.setCreateTime(now);
            confirmOrder.setUpdateTime(now);
            confirmOrderMapper.insert(confirmOrder);
        }else{
            //更新
            confirmOrder.setUpdateTime(now);
            confirmOrderMapper.updateByPrimaryKey(confirmOrder);
        }


    }
    // 分页查询确认订单列表
    public PageResp<ConfirmOrderQueryResp> queryList(ConfirmOrderQueryReq req){
        ConfirmOrderExample example = new ConfirmOrderExample();
        example.setOrderByClause("update_time desc");
        ConfirmOrderExample.Criteria criteria = example.createCriteria();
        //if(ObjUtil.isNotNull(req.getMemberId())){
            //criteria.andMemberIdEqualTo(req.getMemberId());
        //}

        log.info("查询页码:{}",req.getPage());
        log.info("每页条数:{}",req.getSize());
        PageHelper.startPage(req.getPage(),req.getSize());
        List<ConfirmOrder> confirmOrderList = confirmOrderMapper.selectByExampleWithBLOBs(example);

        PageInfo<ConfirmOrder> pageInfo = new PageInfo<>(confirmOrderList);
        log.info("总条数:{}", pageInfo.getTotal());
        log.info("总页数:{}", pageInfo.getPages());

        List<ConfirmOrderQueryResp> list = BeanUtil.copyToList(confirmOrderList, ConfirmOrderQueryResp.class);

        PageResp<ConfirmOrderQueryResp> pageResp = new PageResp<>();
        pageResp.setList(list);
        pageResp.setTotal(pageInfo.getTotal());

        return pageResp;
    }
    //删除确认订单
    public void delete(Long id){
        confirmOrderMapper.deleteByPrimaryKey(id);
    }

    // 购票逻辑
    @SentinelResource(value = "doConfirm", blockHandler = "doConfirmBlock")
    public void doConfirm(ConfirmOrderMQDdto dto) throws InterruptedException {
        MDC.put("LOG_ID", dto.getLogId());
        log.info("异步出票开始:{}",dto);

        //获取车次锁
        String lockKey = RedisKeyPreEnum.CONFIRM_ORDER + "-" + dto.getDate()+"-"+dto.getTrainCode();
        RLock lock = redissonClient.getLock(lockKey);
        boolean tryLock = lock.tryLock(0, TimeUnit.SECONDS);
        if (tryLock){
            log.info("抢到redis的锁:{}", lockKey);
        }else{
            log.info("没抢到锁，有其它消费线程正在出票，不做任何处理");
            return;
        }

        try {
            //从数据库查confirm_order,循环处理状态为INIT的订单
            while(true){
                ConfirmOrderExample example = new ConfirmOrderExample();
                example.setOrderByClause("id asc");
                example.createCriteria()
                        .andDateEqualTo(dto.getDate())
                        .andTrainCodeEqualTo(dto.getTrainCode())
                        .andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
                PageHelper.startPage(1,5);
                List<ConfirmOrder> confirmOrderList = confirmOrderMapper.selectByExampleWithBLOBs(example);

                if(CollUtil.isEmpty(confirmOrderList)){
                    log.info("没有需要处理的订单，结束循环");
                    break;
                }else{
                    log.info("本次处理{}条确认订单",confirmOrderList.size());
                }
                //一条一条的卖
                confirmOrderList.forEach(confirmOrder -> {
                    try {
                        sell(confirmOrder);
                    }catch (BusinessException e){
                        if (e.getE().equals(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR)){
                            log.info("本订单余票不足，继续出售下一个订单");
                            confirmOrder.setStatus(ConfirmOrderStatusEnum.EMPTY.getCode());
                            updateStatus(confirmOrder);
                        }else{
                            throw e;
                        }
                    }
                });
            }
        }finally {
            log.info("购票流程结束，释放锁！lockKey:{}",lockKey);
            lock.unlock();
        }
    }
    private void getSeat(List<DailyTrainSeat> finalSeatList, Date date, String trainCode, String seatType, String column, List<Integer> offSetList,  Integer startIndex, Integer endIndex){
        List<DailyTrainSeat> getSeatList = new ArrayList<>();
        if (StrUtil.isNotBlank(column)){
            column = column.split("")[0];
        }
        List<DailyTrainCarriage> carriageList = dailyTrainCarriageService.selectBySeatType(trainCode, date, seatType);
        log.info("共查出{}个符合条件的车厢",carriageList.size());
        // 一个一个车厢获取数据
        for(DailyTrainCarriage carriage : carriageList){
            getSeatList = new ArrayList<>();
            List<DailyTrainSeat> seatList = dailyTrainSeatService.selectByCarriage(date, trainCode, carriage.getIndex());
            log.info("开始从车厢{}选座，该车厢座位数为{}",  carriage.getIndex(), seatList.size());

            for(DailyTrainSeat seat : seatList){
                String col = seat.getCol();
                //判断当前座位不能被选中过
                boolean alreadyChooseFlag = false;
                for (DailyTrainSeat finalSeat : finalSeatList){
                    if (finalSeat.getId().equals(seat.getId())) {
                        alreadyChooseFlag = true;
                        break;
                    }
                }
                if(alreadyChooseFlag){
                    log.info("座位{}已经被选择过，不能重复选中，继续判断下一个座位",seat.getCarriageSeatIndex());
                    continue;
                }
                //若未选座或列值不匹配，跳出循环
                if(StrUtil.isNotBlank(column)){
                    if(!column.equals(col)){
                        log.info("座位{}与用户选座列值不匹配，跳过，当前列值{}，目标列值{}", seat.getCarriageSeatIndex(), col, column);
                        continue;
                    }
                }

                Boolean ifChose = calSell(seat, startIndex, endIndex);
                if(ifChose){
                    log.info("选中座位");
                    getSeatList.add(seat);
                }else{
                    continue;
                }

                // 根据offset选剩下的座位
                boolean isGetAllOffsetSeat = true;
                if(CollUtil.isNotEmpty(offSetList)){
                    log.info("有偏移值{}，校验偏移的座位是否可选",offSetList);
                    for (int i = 1; i < offSetList.size() ; i++) {
                        int nextIndex = seat.getCarriageSeatIndex() + offSetList.get(i)-1;

                        if(nextIndex >= seatList.size()){
                            log.info("座位{}偏移后的索引超出这个车厢座位数，跳过",  nextIndex);
                            isGetAllOffsetSeat = false;
                            break;
                        }

                        DailyTrainSeat nextDailyTrainSeat = seatList.get(nextIndex);
                        Boolean ifChoseNext = calSell(nextDailyTrainSeat, startIndex, endIndex);
                        if(ifChoseNext){
                            log.info("座位{}被选中",  nextDailyTrainSeat.getCarriageSeatIndex());
                            getSeatList.add(nextDailyTrainSeat);
                        }else{
                            log.info("座位{}不可选中",  nextDailyTrainSeat.getCarriageSeatIndex());
                            isGetAllOffsetSeat = false;
                            break;
                        }
                    }
                }
                if(isGetAllOffsetSeat){
                    if(StrUtil.isNotBlank(column)){
                        log.info("所有偏移座位已选完");
                    }
                }else{
                    log.info("偏移座位有不可选的，继续重新选第一个座位");
                    getSeatList = new ArrayList<>();
                    continue;
                }
                // 保存选好的座位
                finalSeatList.addAll(getSeatList);
                return;
            }
        }
    }
    //更新confirm_order
    private void updateStatus(ConfirmOrder confirmOrder){
        ConfirmOrder confirmOrderForUpdate = new ConfirmOrder();
        confirmOrderForUpdate.setId(confirmOrder.getId());
        confirmOrderForUpdate.setStatus(confirmOrder.getStatus());
        confirmOrderForUpdate.setUpdateTime(new Date());
        confirmOrderMapper.updateByPrimaryKeySelective(confirmOrderForUpdate);
    }
    //购票逻辑
    private void sell(ConfirmOrder confirmOrder){
        try{
            Thread.sleep(2000);
        }catch (InterruptedException e){
            throw new RuntimeException(e);
        }
        //构造ConfirmOrderDoReq
        ConfirmOrderDoReq req = new ConfirmOrderDoReq();
        req.setMemberId(confirmOrder.getMemberId());
        req.setDate(confirmOrder.getDate());
        req.setTrainCode(confirmOrder.getTrainCode());
        req.setStart(confirmOrder.getStart());
        req.setEnd(confirmOrder.getEnd());
        req.setDailyTrainTicketId(confirmOrder.getDailyTrainTicketId());
        req.setTickets(JSON.parseArray(confirmOrder.getTickets(), ConfirmOrderTicketReq.class));
        req.setImageCode("");
        req.setImageCodeToken("");
        req.setLogId("");
        //订单状态设为处理中
        log.info("订单状态设为处理中，避免重复处理,confirm_order.id:{}",confirmOrder.getId());
        confirmOrder.setStatus(ConfirmOrderStatusEnum.PENDING.getCode());
        updateStatus(confirmOrder);
        //获取余票信息，参数为日期，车次编号，出发站，到达站
        DailyTrainTicket dailyTrainTicket = dailyTrainTicketService.selectByUnique(req.getDate(), req.getTrainCode(), req.getStart(), req.getEnd());
        log.info("查出余票记录：{}", dailyTrainTicket);
        //扣减余票，判断余票数量是否充足
        reduceTickets(req, dailyTrainTicket);
        //最终的选座结果
        List<DailyTrainSeat> finalSeatList = new ArrayList<>();
        //计算座位偏移
        //如选择A1，B1，C1，偏移量为[0,1,2]
        if (StrUtil.isBlank(req.getTickets().get(0).getSeat())) {
            log.info("本次购票没有选座");
            //为每张票进行选座
            for(ConfirmOrderTicketReq ticketReq : req.getTickets()){
                getSeat(finalSeatList, req.getDate(),  req.getTrainCode(), ticketReq.getSeatTypeCode(), null, null, dailyTrainTicket.getStartIndex(),dailyTrainTicket.getEndIndex());
            }
        }else{
            log.info("本次购票有选座");
            List<SeatColEnum> colEnumList = SeatColEnum.getColsByType(req.getTickets().get(0).getSeatTypeCode());
            log.info("本次选座座位类型包含列为：{}", colEnumList);
            List<String> referSeatList = new ArrayList<>();
            for (int i = 0; i < 2; i++) {
                for(SeatColEnum colEnum : colEnumList){
                    referSeatList.add(colEnum.getCode()+(i+1));
                }
            }
            log.info("用于做参照的两排座位：{}", referSeatList);
            List<Integer> offSetList = new ArrayList<>();
            List<Integer> absoluteOffSetList = new ArrayList<>();
            for(ConfirmOrderTicketReq ticketReq : req.getTickets()){
                int index = referSeatList.indexOf(ticketReq.getSeat());
                absoluteOffSetList.add(index);
            }
            log.info("座位绝对偏移量：{}", absoluteOffSetList);
            for(Integer index :  absoluteOffSetList){
                int offset = index - absoluteOffSetList.get(0);
                offSetList.add(offset);
            }
            log.info("座位相对第一个选中座位的偏移量：{}", offSetList);
            getSeat(finalSeatList, req.getDate(), req.getTrainCode(), req.getTickets().get(0).getSeatTypeCode(), req.getTickets().get(0).getSeat(), offSetList, dailyTrainTicket.getStartIndex(),  dailyTrainTicket.getEndIndex());
        }
        log.info("最终选座：{}",finalSeatList);
        try {
            //写入数据库
            afterConfirmOrderService.afterDoConfirm(finalSeatList,dailyTrainTicket,req.getTickets(),confirmOrder);
        } catch (Exception e) {
            log.error("保存购票信息失败：{}", e);
            throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_EXCEPTION);
        }
    }
    // 查看该座位是否可卖
    private Boolean calSell(DailyTrainSeat dailyTrainSeat,Integer startIndex, Integer endIndex){
        String sell = dailyTrainSeat.getSell();
        String sellPart = sell.substring(startIndex, endIndex);
        if (Integer.parseInt(sellPart)>0){
            log.info("座位{}在区间{}-{}已售过票，不可选中该座位",  dailyTrainSeat.getCarriageSeatIndex(), startIndex, endIndex);
            return false;
        }else{
            log.info("座位{}在区间{}-{}未售过票，可选中该座位",  dailyTrainSeat.getCarriageSeatIndex(), startIndex, endIndex);
            String curSell = sellPart.replace('0','1');
            curSell = StrUtil.fillBefore(curSell,'0',endIndex);
            curSell = StrUtil.fillAfter(curSell,'0',sell.length());

            int newSellInt = NumberUtil.binaryToInt(curSell) | NumberUtil.binaryToInt(sell);
            String newSell = NumberUtil.getBinaryStr(newSellInt);
            newSell  = StrUtil.fillBefore(newSell,'0',sell.length());

            log.info("座位{}被选中，原售票信息：{}，车站区间{}-{}，即{}，最终售票信息：{}", dailyTrainSeat.getCarriageSeatIndex(), sell, startIndex, endIndex, curSell, newSell);
            dailyTrainSeat.setSell(newSell);

            return true;
        }
    }
    // 减票
    private static void reduceTickets(ConfirmOrderDoReq req, DailyTrainTicket dailyTrainTicket) {
        // 检查余票是否足够
        for(ConfirmOrderTicketReq ticketReq : req.getTickets()){
            String seatTypeCode = ticketReq.getSeatTypeCode();
            SeatTypeEnum seat = EnumUtil.getBy(SeatTypeEnum::getCode, seatTypeCode);
            switch (seat){
                case YDZ->{
                    int countLeft = dailyTrainTicket.getYdz()-1;
                    if (countLeft < 0){
                        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                    }
                    // 扣减票数量后重新赋值，方便下一张票进行余票数量检查
                    dailyTrainTicket.setYdz(countLeft);
                }
                case EDZ->{
                    int countLeft = dailyTrainTicket.getEdz()-1;
                    if (countLeft < 0){
                        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                    }
                    dailyTrainTicket.setEdz(countLeft);
                }
                case YW->{
                    int countLeft = dailyTrainTicket.getYw()-1;
                    if (countLeft < 0){
                        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                    }
                    dailyTrainTicket.setYw(countLeft);
                }
                case RW->{
                    int countLeft = dailyTrainTicket.getRw()-1;
                    if (countLeft < 0){
                        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                    }
                    dailyTrainTicket.setRw(countLeft);
                }

            }
        }
    }
    /*
    * 需要包含限流方法的所有参数和BlockException参数
    * */
    public void doConfirmBlock(ConfirmOrderDoReq  req, BlockException e){
        log.info("购票被限流:{}", req);
        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_FLOW_EXCEPTION);
    }

    public Integer queryLineCount(Long id) {
        ConfirmOrder confirmOrder = confirmOrderMapper.selectByPrimaryKey(id);
        ConfirmOrderStatusEnum statusEnum = EnumUtil.getBy(ConfirmOrderStatusEnum::getCode, confirmOrder.getStatus());
        int result = switch (statusEnum){
            case PENDING -> 0;//出票中
            case SUCCESS -> -1;
            case FAILURE -> -2;
            case EMPTY -> -3;
            case CANCEL -> -4;
            case INIT -> 999;
        };
        //查询排在第几位
        if (result==999){
            ConfirmOrderExample example = new ConfirmOrderExample();
            example.or().andDateEqualTo(confirmOrder.getDate())
                    .andTrainCodeEqualTo(confirmOrder.getTrainCode())
                    .andCreateTimeLessThan(confirmOrder.getCreateTime())
                    .andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
            example.or().andDateEqualTo(confirmOrder.getDate())
                    .andTrainCodeEqualTo(confirmOrder.getTrainCode())
                    .andCreateTimeLessThan(confirmOrder.getCreateTime())
                    .andStatusEqualTo(ConfirmOrderStatusEnum.PENDING.getCode());
            return Math.toIntExact(confirmOrderMapper.countByExample(example));
        }else{
            return result;
        }
    }
}
